/*
 * Central functions of `HRV64Generator` and `RV64Funct`
 * to generate RV64 code from HXD1 IR.
 */
#include "HRV64Generator.h"
#include "HExceptions.h"

// Don't use these flags of temp!
#define fassigned ERROR
#define frefed ERROR
#define lrefed ERROR

/*
 * Create a string for a temp `t`.
 * Used to append comment information.
 */
static inline std::string * str_temp(HIRTempno t)
{
	std::string *str = new std::string("%");
	*str += std::to_string(t);
	return str;
}

/*
 * Append instructions to load temporary `temp` to GPR/FPR `reg`.
 * `temp` may be on the stack, have static value (stack location)
 * or already in a reg.
 * Make sure that `reg` is available.
 * This function will NOT modify the desc of `reg` or `temp`.
 * NOTE: moving between GPR and FPR is NOT safe and should be careful!
 */
void RV64Funct::load_temp_to_reg(HIRTempno temp, RV64GFR reg)
{
	size_t iidx;
	HIRBtype ttype = ircode_->funct(hirfidx_).temps.at(temp).type;
	std::string *cominfo = nullptr;
	
	if (tdescs_.at(temp).status & TempDesc::REG)
	{
		RV64GFR rs = tdescs_.at(temp).reg;
		if (rs != reg)
		{
			if (RV64GFR_is_GPR(rs))
			{	// source is GPR
				if (RV64GFR_is_GPR(reg))
					append_move(rs, reg);
				else	// GPR to FPR: suppose that size is `D` temporarily
					append_fmv4i(RV64Opsz::D, rs, reg);
			}
			else
			{	// source is FPR
				if (RV64GFR_is_GPR(reg))
					append_fmv2i(RV64Opsz::D, rs, reg);
				else
				/*
				 * For temp whose type is `FLOAT`, we use `fmv.s`;
				 * Otherwise we copy the bit pattern using `fmv.d`.
				 */
					append_fmove(
						ttype == HIRBtype::FLOAT ? RV64Opsz::S : RV64Opsz::D,
						RV64Oper::FMV, rs, reg
					);
			}
		}
	}
	else if (tdescs_.at(temp).status & TempDesc::STATIC)
	{	// the temp has static value (stack location)
		cominfo = opts.debug_level != 0 ? str_temp(temp) : nullptr;
		iidx = append_arloi(RV64Oper::ADDi, RV64GFR::SP,
			uint64_t(tdescs_.at(temp).stval), reg, RV64Opsz::D,
			cominfo);
		stack_access_list_.push_back(iidx);
	}
	else if (tdescs_.at(temp).status & TempDesc::STACK)
	{	// the temp is on the stack
		cominfo = opts.debug_level != 0 ? str_temp(temp) : nullptr;
		if (!RV64GFR_is_GPR(reg))
			iidx = append_fload(
				ttype == HIRBtype::FLOAT ? RV64Opsz::S : RV64Opsz::D,
				RV64GFR::SP, reg, uint64_t(tdescs_.at(temp).stloc),
				cominfo
			);
		else
			iidx = append_load(
				hirbtype2rv64opsz(ttype), RV64GFR::SP,
				reg, uint64_t(tdescs_.at(temp).stloc),
				cominfo
			);
		stack_access_list_.push_back(iidx);
	}
	else
		throw std::logic_error(
			std::string("Cannot find temp ") + std::to_string(temp));
}

/*
 * Evict temp `t` from the reg (GPR/FPR) that it occupies
 * by writeing it back to stack.
 * And release the reg (by clearing its reg desc).
 * Also clear `REG` in temp desc of `t`.
 * `bbidx` is the index of the current BB.
 */
void RV64Funct::temp_writeback(HIRTempno t, uint32_t bbidx)
{
	size_t iidx;
	std::string *cominfo = nullptr;
	auto td = tdescs_.begin() + t;
	HIRBtype ttype = ircode_->funct(hirfidx_).temps.at(t).type;

	if ((td->status & TempDesc::REG) == 0)
		throw std::logic_error(
			std::string("Evicting temp ") + std::to_string(t)
			+ " that doesn't occupy reg"
		);
	if (td->status & (TempDesc::STACK | TempDesc::STATIC))
	{	// no need to store it
		// In normal situations, `status` would not have both `REG` and `STATIC`
		td->status = td->status & ~TempDesc::REG;
	}
	else
	{	// should store to stack
		uint32_t tsize = hirsize(ircode_->funct(hirfidx_).temps.at(t).type);
		/*
		 * NOTE: if we allow an integer temp be stored in FPR in the future,
		 * `tsize` should be set as `RV64_GFR_SIZE` if the temp
		 * is not floating-point but `td->reg` is FPR.
		 */
		if (td->stloc == INVALID_STLOC)
		{	// has NOT been allocated
			if (ircode_->funct(hirfidx_).temps.at(t).localof != bbidx)
				/*
				 * As we don't implemente reg assignment, a non-local temp
				 * must have allocated stack address.
				 */
				throw std::logic_error(
					std::string("temp ") + std::to_string(t) + " is not local of"
					+ std::to_string(bbidx) + " but not allocated on the stack"
				);
			td->stloc = allocate_unit(tsize, tsize, bbidx);
		}
		cominfo = opts.debug_level != 0 ? str_temp(t) : nullptr;
		if (ttype == HIRBtype::DOUBLE || ttype == HIRBtype::FLOAT)
		{
			if (RV64GFR_is_GPR(td->reg))
				throw std::logic_error(
					std::string("temp ") + std::to_string(t) + "is floating-point"
					"but stored in GPR" + std::to_string(uint32_t(td->reg))
				);
			iidx = append_fstore(
				ttype == HIRBtype::FLOAT ? RV64Opsz::S : RV64Opsz::D,
				RV64GFR::SP, td->reg, uint64_t(td->stloc), cominfo
			);
		}
		else
		{	// temp `t` is integer
			if (RV64GFR_is_GPR(td->reg))
				iidx = append_store(hirbtype2rv64opsz(ttype),
					RV64GFR::SP, td->reg, uint64_t(td->stloc), cominfo);
			else
				throw std::logic_error(
					std::string("temp ") + std::to_string(t) + "is integer"
					"but stored in FPR" + std::to_string(uint32_t(td->reg))
				);
			/*
			 * NOTE: if we allow an integer temp be stored in FPR in the future,
			 * `append_fstore(D, ...)` may also be used.
			 */
		}
		/*
		 * Releasing an allocated temp is allowed when dealing `call` instruction.
		 * In this case, status of the temp became `STACK | ALLOCATED`
		 * TEMPoratily and should be restored later (after the `call`).
		 */
		td->status = TempDesc::STACK | (td->status & TempDesc::ALLOCATED);
		stack_access_list_.push_back(iidx);
	}
	rdescs_[unsigned(td->reg)].status = RegDesc::FREE;
}

/*
 * Select regs for temps `dest` and in `srcs`, and store
 * them in `RV64regs_t.reg[3]` and `.reg[0], .reg[1]`, `.reg[2]`.
 * `inst_idx` and `bb_idx` are the indexes of current instruction and BB, respectively.
 * If a temp in `srcs` is invalid, a GPR/FPR (set the corresponding flag in `fp_src`
 * to get a FPR) will only be allocated with there desc cleared.
 * Otherwise, the temp will be loaded to the reg and the desc will be set.
 * If `dest` is invalid, it will be ignored.
 * Only when two temps in `srcs` are valid and the same can they get the same reg!
 * If `allow_dest_reuse` is set as `false`, reg for `dest` will NOT
 * share the same reg as those for temps in `srcs` EXCEPT `dest` is
 * the same temp as one in `srcs`.
 */
uint64_t RV64Funct::get_regs(uint32_t inst_idx, uint32_t bb_idx, HIRTempno dest,
		const std::vector<HIRTempno>& srcs, const bool *fp_src,
		bool allow_dest_reuse)
{
	RV64regs_t regs;
	size_t tidx;
	// used to scan regs
	const RV64GFR *regs_order;
	uint32_t nregs;
	// the pointed GPR/FPR will be spilled
	static uint32_t spill_pointer_GPR = 0u, spill_pointer_FPR = 0u;

	std::vector<HIRTempno> temps = srcs;
	if (dest != INVALID_TEMP_NO)
		temps.push_back(dest);

	for (tidx = 0ul; tidx < sizeof(uint64_t) / sizeof(uint16_t); ++tidx)
		regs.reg[tidx] = uint16_t(RV64GFR::ZERO);

	bool flag_for_dest = false;
	for (tidx = 0ul; tidx < temps.size(); ++tidx)
	{
		flag_for_dest = (dest != INVALID_TEMP_NO
			&& (tidx + 1ul == temps.size()));
		HIRTempno target_temp = temps.at(tidx);
		if (flag_for_dest)
		// write the reg for `dest` to the last element
			tidx = sizeof(uint64_t) / sizeof(uint16_t) - 1ul;
		HIRBtype ttype =
			(target_temp == INVALID_TEMP_NO)
			/*
			 * If `target_temp` is invalid (must in `srcs`),
			 * select GPR or FPR according to flag in `fp_src`.
			 */
			? (fp_src[tidx] ? HIRBtype::DOUBLE : HIRBtype::I32)
			: ircode_->funct(hirfidx_).temps.at(target_temp).type;
		// Hold the selected reg, `ZERO` means not successful
		RV64GFR reg_selected = RV64GFR::ZERO;
		if (target_temp != INVALID_TEMP_NO &&
			(tdescs_.at(target_temp).status & (TempDesc::REG | TempDesc::ALLOCATED)) != 0)
		{	// already in a reg or has been allocated
			/*
			 * Only when two temps in `srcs` or `dest` are valid and the same
			 * can they get the same reg!
			 */
			//regs.reg[tidx] = uint16_t(_tdescs.at(target_temp).reg);
			//continue;
			/*
			 * Just `continue` is wrong because setting descriptors
			 * of the temp and reg cannot be skipped!!!
			 */
			reg_selected = tdescs_.at(target_temp).reg;
		}
		// prepare to scan GPR/FPR
		regs_order = (ttype == HIRBtype::DOUBLE || ttype == HIRBtype::FLOAT
			? HRV64Generator::FPR_order : HRV64Generator::GPR_order);
		nregs = (ttype == HIRBtype::DOUBLE || ttype == HIRBtype::FLOAT
			? uint32_t(RV64GFR::NGFR) - uint32_t(RV64GFR::NGPR) : uint32_t(RV64GFR::NGPR));
		uint32_t i, j;
		// search free GPR/FPR
		if (reg_selected == RV64GFR::ZERO)
			for (i = 0u; i < nregs && regs_order[i] != RV64GFR::ZERO; ++i)
				if (rdescs_[uint32_t(regs_order[i])].flag_allocated == false
					&& rdescs_[uint32_t(regs_order[i])].status == RegDesc::FREE)
				{
					if (!flag_for_dest || !allow_dest_reuse)
					{	// Don't allocate the same reg for temps in `srcs`!
						for (j = 0ul; j < tidx; ++j)
							if (uint16_t(regs_order[i]) == regs.reg[j])
							/*
							 * if `srcs`'s size is less than 3 and `flag_for_dest` is `true`
							 * (thus `tidx` is 3), when `j` is greater than or equal to
							 * `srcs.size()`, `regs.reg[j]` will be skipped automatically
							 * because we have first set it as `RV64GFR::ZERO`.
							 */
								break;
						if (j < tidx)
							continue;
					}
					reg_selected = regs_order[i]; break;	
				}
		HIRTempno v;
		if (reg_selected == RV64GFR::ZERO)
		{	// no free reg is found
			// Try to find a reg holding a temp that will not be used
			for (i = 0u; i < nregs && regs_order[i] != RV64GFR::ZERO; ++i)
			{
				if (rdescs_[uint32_t(regs_order[i])].flag_allocated == true
					|| rdescs_[uint32_t(regs_order[i])].status != RegDesc::TEMP)
					continue;
				if (!flag_for_dest || !allow_dest_reuse)
				{	// Don't allocate the same reg for temps in `srcs`!
					for (j = 0ul; j < tidx; ++j)
						if (uint16_t(regs_order[i]) == regs.reg[j])
							break;
					if (j < tidx)
						continue;
				}
				v = rdescs_[uint32_t(regs_order[i])].tempno;
				// insert a check
				if ((tdescs_[v].status & TempDesc::REG) == 0)
					throw std::logic_error(
						std::string("temp ") + std::to_string(v) +
						" held by reg " + std::to_string(uint32_t(regs_order[i]))
						+ " has no REG in its tempdesc"
					);
				uint32_t future_use = 3u;
				future_use &= ircode_->funct(hirfidx_).get_temp_future_use(v,
					/*
					 * For the reg for `dest`, the future use can be scanned from the
					 * next instruction, as the reg being used in the current instruction
					 * is actually before being assigned here.
					 */
					inst_idx + (flag_for_dest ? 1u : 0u),
					bb_idx
				);
				if (future_use == 0u || future_use == 2u)
				{	// 0: never; 2: assigned before referenced
					reg_selected = regs_order[i];
					if (future_use == 0u && (tdescs_[v].status == TempDesc::REG)
						&& ircode_->funct(hirfidx_).temps.at(v).localof == INVALID_BB_IDX)
						// non-local temp `v` is modified, so write it back to stack
						temp_writeback(v, bb_idx);
				}
			}
		}
		// if failed, try to find a reg whose contents can be found on other
		if (reg_selected == RV64GFR::ZERO)
		{
			for (i = 0u; i < nregs && regs_order[i] != RV64GFR::ZERO; ++i)
			{
				const RegDesc & rdesc = rdescs_[uint32_t(regs_order[i])];
				if (rdesc.flag_allocated)
					continue;
				if (!flag_for_dest || !allow_dest_reuse)
				{	// Don't allocate the same reg for temps in `srcs`!
					for (j = 0ul; j < tidx; ++j)
						if (uint16_t(regs_order[i]) == regs.reg[j])
							break;
					if (j < tidx)
						continue;
				}
				/*
				 * If a reg doesn't hold a temp (holds an address of symbol)
				 * or holds a temp that can be found on the stack or has static value,
				 * just release it.
				 */
				if (rdesc.status != RegDesc::TEMP || 
					((tdescs_[rdesc.tempno].status &
						(TempDesc::STACK | TempDesc::STATIC)) != 0)
				)
				{	reg_selected = regs_order[i]; break;	}
			}
		}
		// if failed, spill one reg holding a temp
		if (reg_selected == RV64GFR::ZERO)
		{
			uint32_t *psp = (ttype == HIRBtype::DOUBLE || ttype == HIRBtype::FLOAT
				? &spill_pointer_FPR : &spill_pointer_GPR);
			do {
				if (rdescs_[uint32_t(reg_selected)].flag_allocated == false
					&& rdescs_[uint32_t(reg_selected)].status == RegDesc::TEMP)
				{
					v = rdescs_[uint32_t(reg_selected)].tempno;
					reg_selected = regs_order[*psp];
				}
				else
					/*
					 * A flag for failure to cause trying again.
					 * If a reg that is free or holding a symbol is available,
					 * it would have been selected in the previous steps.
					 * So we only need to consider spilling regs holding temp here!
					 */
					reg_selected = RV64GFR::ZERO;
				// move the pointer so that it points to the next reg
				if (++*psp >= nregs || regs_order[*psp] == RV64GFR::ZERO)
					*psp = 0u;
				// Don't allocate the same reg for temps in `srcs`!
				for (j = 0ul; j < tidx; ++j)
					if (uint16_t(reg_selected) == regs.reg[j])
						break;
			} while (reg_selected == RV64GFR::ZERO ||
			// If this is not for `dest`, don't spill the reg we have just allocated!
				((!flag_for_dest || !allow_dest_reuse) && j < tidx));
			temp_writeback(v, bb_idx);
		}
		// Now `reg_selected` should be valid
		regs.reg[tidx] = uint16_t(reg_selected);
		if (RV64GFR_is_callee_saved(reg_selected))
			saved_regs_[reg_selected] = INVALID_STLOC;

		if (rdescs_[uint32_t(reg_selected)].status == RegDesc::TEMP
			&& rdescs_[uint32_t(reg_selected)].tempno != target_temp
			&& (tdescs_.at(rdescs_[uint32_t(reg_selected)].tempno).status
				& TempDesc::ALLOCATED) == 0)
			/*
			 * If the reg held a DIFFERENT temp before,
			 * clear the `REG` flag in the temp descriptor of it.
			 * But if the temp is also allocated this register (coalesced),
			 * don't clear its `REG` flag.
			 */
			tdescs_.at(rdescs_[uint32_t(reg_selected)].tempno).status &= ~TempDesc::REG;
		/*
		 * The two (`if`) operations (above and below) canNOT be exchaged
		 * because `target_temp` and `_rdescs[uint32_t(reg_selected)].tempno`
		 * may be the same!
		 */
		if (target_temp != INVALID_TEMP_NO)
		{	// set desc of the temp
			tdescs_.at(target_temp).reg = reg_selected;
			if (!flag_for_dest)
			{
				load_temp_to_reg(target_temp, reg_selected);
				tdescs_.at(target_temp).status |= TempDesc::REG;
			}
			else
				tdescs_.at(target_temp).status = TempDesc::REG
				// keep the flag of `ALLOCATED`!
					| (tdescs_.at(target_temp).status & TempDesc::ALLOCATED);
		}

		// set the descriptor of the selected reg
		if (target_temp == INVALID_TEMP_NO)
			/*
			 * If `dest` is invalid, this function will not take the
			 * responsibility to set its reg desc. So just clear it.
			 */
			rdescs_[uint32_t(reg_selected)].status = RegDesc::FREE;
		else
		{
			rdescs_[uint32_t(reg_selected)].status = RegDesc::TEMP;
			rdescs_[uint32_t(reg_selected)].tempno = target_temp;
		}
	}
	return regs.regs;
}

/*
 * Release the specified GPR/FPR `reg` forcibly.
 * If the reg is occupied, evict the temp occuping it.
 * This is used to get GPR for a function argument.
 * Return the temp number if it is holding a temp
 * or `INVALID_TEMP_NO` otherwise.
 */
HIRTempno RV64Funct::get_regs(RV64GFR reg, uint32_t bbidx)
{
	HIRTempno rt = INVALID_TEMP_NO;
	RegDesc &rd = rdescs_[uint32_t(reg)];
	if (rd.status == RegDesc::RESERVED)
		throw std::logic_error(std::string("Cannot release reserved reg ")
			+ std::to_string(uint32_t(reg)));
	else if (rd.status == RegDesc::TEMP)
		temp_writeback(rt = rd.tempno, bbidx);
	else if (rd.status == RegDesc::XSYM)
		rd.status = RegDesc::FREE;
	// If the status is `FREE`, do nothing.

	if (RV64GFR_is_callee_saved(reg))
		saved_regs_[reg] = INVALID_STLOC;
	return rt;
}

/*
 * Generate RV64IMFD code for a BB.
 */
void RV64Funct::generate_bb_code(uint32_t bbidx)
{
	uint32_t bb_begin = ircode_->funct(hirfidx_).basic_blocks.at(bbidx).begin,
		bb_end = ircode_->funct(hirfidx_).basic_blocks.at(bbidx).end,
		// `iidx0` is the index of the current instruction while `iidx1` is the next
		iidx0, iidx1;
	const std::vector<HIRInst>& funct_ir = ircode_->funct(hirfidx_).ircode;
	std::vector<HIRInst>::const_iterator
		inst0 = funct_ir.begin() + bb_begin, inst1;
	// For `get_regs()` function
	std::vector<HIRTempno> src_temps;
	bool fp_src[sizeof(uint64_t) / sizeof(uint16_t)];
	RV64regs_t regs_got;
	/*
	 * If temps are saved to the stack because of branch or return instruction,
	 * this flag will be set to `true`.
	 * Otherwise it will be kept `false`, so `save_temps_to_stack()`
	 * should be called in the end.
	 */
	bool temps_are_saved = false;
	// comment information
	std::string *cominfo = nullptr;

	// Initialize the `sp` of this BB
	bb_stacks_.at(bbidx).sp = static_top_;

	reset_desc(bbidx);

	for (iidx0 = bb_begin, inst0 = funct_ir.begin() + iidx0; iidx0 <= bb_end;
		inst0 = inst1, iidx0 = iidx1)
	{
		src_temps.clear();
		// set an iterator pointing to the next instruction...
		for (iidx1 = iidx0 + 1u; iidx1 < funct_ir.size()
			&& funct_ir.at(iidx1).oper == HIROper::EMPTY_LINE; ++iidx1)
			;
		if (iidx1 < funct_ir.size())
		/*
		 * If `inst0` is the last instruction of the function,
		 * `inst1` will be the same as `inst0`!
		 */
			inst1 = funct_ir.begin() + iidx1;
		else
			inst1 = inst0;
		// string prefix for this instruction
		const std::string inststr = "inst " + std::to_string(iidx0) + ": ";
		HIROper op = inst0->oper;
		HIRBtype btype = inst0->type.base_type;
		switch (op)
		{
		case HIROper::RET: 
		{
			if (btype == HIRBtype::FLOAT || btype == HIRBtype::DOUBLE)
			{ 	// floating-point return value
				if (inst0->value.tp == HIROpnd::TEMP)
					load_temp_to_reg(inst0->value.temp, RV64GFR::FA0);
				else if (inst0->value.tp == HIROpnd::IMM)
				{
					const float *pfval = &(inst0->value.imm.fval);
					append_ldimm(RV64GFR::T0,
						btype == HIRBtype::FLOAT ? *(const int32_t*)(pfval)
						: inst0->value.imm.ival);
					append_fmv4i(btype == HIRBtype::FLOAT ? RV64Opsz::S : RV64Opsz::D,
						RV64GFR::T0, RV64GFR::FA0);
				}
				else
					throw std::logic_error(inststr + "invalid value.tp");
			}
			else
			{
				if (inst0->value.tp == HIROpnd::TEMP)
					load_temp_to_reg(inst0->value.temp, RV64GFR::A0);
				else if (inst0->value.tp == HIROpnd::IMM)
					append_ldimm(RV64GFR::A0,
						btype == HIRBtype::I8 ? int8_t(inst0->value.imm.ival)
						: int32_t(inst0->value.imm.ival)
					);
				else
					append_ldaddr(RV64GFR::A0, inst0->value.xvaridx);
			}
			// There is no need to save temps as `ret` is met.
			temps_are_saved = true;
			//save_temps_to_stack(bbidx);
			append_jump(epilogue_);
			// force the `for` loop to exit
			iidx1 = bb_end + 1u;
		}
			break;
		case HIROper::BRc:
		{
			src_temps.push_back(inst0->cond);
			regs_got.regs = get_regs(iidx0, bbidx, INVALID_TEMP_NO, src_temps);
			save_temps_to_stack(bbidx);
			temps_are_saved = true;
			if (inst1->oper != HIROper::LABEL ||
				(inst1->labelno != inst0->iftrue && inst1->labelno != inst0->iffalse))
			{
				append_cbranch(RV64Oper::BNE, RV64GFR(regs_got.reg[0]), RV64GFR::ZERO,
					inst0->iftrue);
				append_jump(inst0->iffalse);
			}
			else// consider the following label
			{
				if (inst1->labelno == inst0->iftrue)
					append_cbranch(RV64Oper::BEQ, RV64GFR(regs_got.reg[0]),
						RV64GFR::ZERO, inst0->iffalse);
				else	// `.. == inst0->iffalse`
					append_cbranch(RV64Oper::BNE, RV64GFR(regs_got.reg[0]),
						RV64GFR::ZERO, inst0->iftrue);
			}
		}
			break;
		case HIROper::BRu:
		{
			save_temps_to_stack(bbidx);
			temps_are_saved = true;
			if (inst1->oper != HIROper::LABEL || inst1->labelno != inst0->brdest)
				append_jump(inst0->brdest);
		}
			break;
		case HIROper::ADD: case HIROper::SUB: case HIROper::SHL: case HIROper::LSHR:
		case HIROper::ASHR: case HIROper::AND: case HIROper::OR: case HIROper::XOR:
		case HIROper::MUL: case HIROper::SDIV: case HIROper::SREM:
		{
			// bit mask for imm(or external variable) flag
			int32_t flag_imm = 0;
			// if `op2` is imm and is not too long, I-Type instruction can be used
			bool flag_use_itype = false;
			RV64Oper rv64op;
			if (inst0->op1.tp != HIROpnd::TEMP) flag_imm |= 1;
			if (inst0->op2.tp != HIROpnd::TEMP) flag_imm |= 2;

			if (op == HIROper::ADD && inst0->flag_garbage)
			{	// garbage initialization
				if ( ircode_->funct(hirfidx_).temps.at(inst0->dest)
					.localof == INVALID_BB_IDX
					|| (3ul & ircode_->funct(hirfidx_).get_temp_future_use(
					inst0->dest, iidx0 + 1u, bbidx)) != 1ul)
				/*
				 * If the garbage initialized temp is non-local (so that it has
				 * been allocated on the stack) or local but is NOT live here,
				 * just ignore this initialization instruction.
				 */
					break;
			}

			// operand 1
			src_temps.push_back((flag_imm & 1) ? INVALID_TEMP_NO : inst0->op1.temp);
			// operand 2
			if (flag_imm & 2)
			{
				/*
				 * If `op1` is not temp, it can be external variable;
				 * but `op2` should be either temp or imm!
				 */
				if (inst0->op2.tp != HIROpnd::IMM)
					throw std::logic_error(inststr + "invalid op2");
				if (op != HIROper::MUL && op != HIROper::SDIV && op != HIROper::SREM
					&& is_imm12(op == HIROper::SUB ? -int32_t(inst0->op2.imm.ival)
					: int32_t(inst0->op2.imm.ival)))
					flag_use_itype = true;
				else
					src_temps.push_back(INVALID_TEMP_NO);
			}
			else
				src_temps.push_back(inst0->op2.temp);
			fp_src[0] = fp_src[1] = false;
			regs_got.regs = get_regs(iidx0, bbidx, inst0->dest, src_temps, fp_src);
			if (flag_imm & 1)
			{
				if (inst0->op1.tp == HIROpnd::XVAR)
					append_ldaddr(RV64GFR(regs_got.reg[0]), inst0->op1.xvaridx);
				else	// imm
					append_ldimm(RV64GFR(regs_got.reg[0]), 
						hirimm2int64(inst0->op1.imm, btype)
					);
			}
			if ((flag_imm & 2) != 0 && !flag_use_itype)
				append_ldimm(RV64GFR(regs_got.reg[1]),
					hirimm2int64(inst0->op2.imm,
					/*
					 * In HXD1 IR, we allow `ptr` to be added by `i32`.
					 * So if `btype` is `POINTER`, we should pass `I32` to
					 * avoid `hirimm2int64()` returning 0.
					 */
					btype == HIRBtype::POINTER ? HIRBtype::I32 : btype)
				);
			rv64op = (
				op == HIROper::ADD ? (flag_use_itype ? RV64Oper::ADDi : RV64Oper::ADD)
				: op == HIROper::SUB ? (flag_use_itype ? RV64Oper::ADDi : RV64Oper::SUB)
				: op == HIROper::SHL ? (flag_use_itype ? RV64Oper::SLLi : RV64Oper::SLL)
				: op == HIROper::LSHR ? (flag_use_itype ? RV64Oper::SRLi : RV64Oper::SRL)
				: op == HIROper::ASHR ? (flag_use_itype ? RV64Oper::SRAi : RV64Oper::SRA)
				: op == HIROper::AND ? (flag_use_itype ? RV64Oper::ANDi : RV64Oper::AND)
				: op == HIROper::OR ? (flag_use_itype ? RV64Oper::ORi : RV64Oper::OR)
				: op == HIROper::MUL ? RV64Oper::MUL
				: op == HIROper::SDIV ? RV64Oper::DIV
				: op == HIROper::SREM ? RV64Oper::REM
				: (flag_use_itype ? RV64Oper::XORi : RV64Oper::XOR)
			);
			cominfo = opts.debug_level != 0 ? str_temp(inst0->dest) : nullptr;
			if (flag_use_itype)
			{	// If coalesced, omit the move instruction.
				if (!((op == HIROper::ADD || op == HIROper::SUB) &&
					regs_got.reg[0] == regs_got.reg[3] && inst0->op2.imm.ival == 0l))
					append_arloi(rv64op, RV64GFR(regs_got.reg[0]),
						op == HIROper::SUB ? -int32_t(inst0->op2.imm.ival)
						: int32_t(inst0->op2.imm.ival), RV64GFR(regs_got.reg[3]),
						RV64Opsz::DEFAULT, cominfo
					);
			}
			else
				append_arlo(rv64op, RV64GFR(regs_got.reg[0]),
					RV64GFR(regs_got.reg[1]), RV64GFR(regs_got.reg[3]),
					RV64Opsz::DEFAULT, cominfo
				);
		}
			break;
		case HIROper::FADD: case HIROper::FSUB: case HIROper::FMUL: case HIROper::FDIV:
		case HIROper::FCMP:
		{
			// if the operand 2 is zero
			bool flag_opnd2_zero = false;
			// bit mask for imm(or external variable) flag
			int32_t flag_imm = 0;
			bool flag_fcmp = (op == HIROper::FCMP);
			RV64Oper rv64op, rv64op_fcmp;
			if (inst0->op1.tp != HIROpnd::TEMP) flag_imm |= 1;
			if (inst0->op2.tp != HIROpnd::TEMP) flag_imm |= 2;
			if ((flag_imm & 2) != 0 && inst0->op2.imm.ival == 0l)
				flag_opnd2_zero = true;

			if (op == HIROper::FADD && inst0->flag_garbage)
			{	// garbage initialization
				if ( ircode_->funct(hirfidx_).temps.at(inst0->dest)
					.localof == INVALID_BB_IDX
					|| (3ul & ircode_->funct(hirfidx_).get_temp_future_use(
					inst0->dest, iidx0 + 1u, bbidx)) != 1ul)
				/*
				 * If the garbage initialized temp is non-local (so that it has
				 * been allocated on the stack) or local but is NOT live here,
				 * just ignore this initialization instruction.
				 */
					break;
			}

			fp_src[0] = fp_src[1] = true;
			fp_src[2] = false;
			// operand 1
			src_temps.push_back((flag_imm & 1) ? INVALID_TEMP_NO : inst0->op1.temp);
			// operand 2
			src_temps.push_back((flag_imm & 2) ? INVALID_TEMP_NO : inst0->op2.temp);
			if (flag_imm != 0 && !flag_fcmp && !(flag_imm == 2 && flag_opnd2_zero))
			{	/*
				 * Another GPR is needed for imm.
				 * But for `fcmp`, as the dest is GPR, it can be reused.
				 * Besides, if `op1` is temp and `op2` is zero, it is also
				 * no need to get a GPR.
				 */
				src_temps.push_back(INVALID_TEMP_NO);
			}
			regs_got.regs = get_regs(iidx0, bbidx, inst0->dest, src_temps, fp_src);
			if (flag_imm & 1)
			{
				if (opts.debug_level != 0)
				{
					if (btype == HIRBtype::DOUBLE)
						cominfo = new std::string(std::to_string(inst0->op1.imm.dval));
					else
						cominfo = new std::string(std::to_string(inst0->op1.imm.fval));
				}
				else
					cominfo = nullptr;
				append_ldimm(RV64GFR(regs_got.reg[flag_fcmp ? 3 : 2]),
					hirimm2int64(inst0->op1.imm, btype), cominfo
				);
				append_fmv4i(hirbtype2rv64opsz(btype),
					RV64GFR(regs_got.reg[flag_fcmp ? 3 : 2]), RV64GFR(regs_got.reg[0]));
			}
			if (flag_imm & 2)
			{
				if (!flag_opnd2_zero)
				{
					append_ldimm(RV64GFR(regs_got.reg[flag_fcmp ? 3 : 2]),
						hirimm2int64(inst0->op2.imm, btype)
					);
					append_fmv4i(hirbtype2rv64opsz(btype),
						RV64GFR(regs_got.reg[flag_fcmp ? 3 : 2]),
						RV64GFR(regs_got.reg[1])
					);
				}
				else if (!(op == HIROper::FADD && regs_got.reg[0] == regs_got.reg[3]
					&& flag_opnd2_zero))
				/*
				 * If the operand 2 is zero, just move `$zero` to FPR.
				 * But adding zero to the SAME FPR is meaningless!
				 */
					append_fmv4i(hirbtype2rv64opsz(btype),
						RV64GFR::ZERO, RV64GFR(regs_got.reg[1])
					);
			}
			rv64op_fcmp = (
				inst0->fcond == FCMPCond::OGE ? RV64Oper::FGE
				: inst0->fcond == FCMPCond::OGT ? RV64Oper::FGT
				: inst0->fcond == FCMPCond::OLE ? RV64Oper::FLE
				: inst0->fcond == FCMPCond::OLT ? RV64Oper::FLT
				/*
				 * for `ONE`, we use `feq.` instruction with another
				 * `xori` instruction to implement.
				 */
				: RV64Oper::FEQ
			);
			rv64op = (
				op == HIROper::FADD ? RV64Oper::FADD : op == HIROper::FSUB ? RV64Oper::FSUB
				: op == HIROper::FMUL ? RV64Oper::FMUL : op == HIROper::FDIV ? RV64Oper::FDIV
				: rv64op_fcmp
			);
			if (flag_fcmp)
			{
				append_fcomp(hirbtype2rv64opsz(btype), rv64op,
					RV64GFR(regs_got.reg[0]), RV64GFR(regs_got.reg[1]),
					RV64GFR(regs_got.reg[3])
				);
				if (inst0->fcond == FCMPCond::ONE)
					append_arloi(RV64Oper::XORi, RV64GFR(regs_got.reg[3]), 1l,
						RV64GFR(regs_got.reg[3])
				);
			}
			else if (!(op == HIROper::FADD && regs_got.reg[0] == regs_got.reg[3]
					&& flag_opnd2_zero))
				// Adding zero to the SAME FPR is meaningless!
				append_farith(hirbtype2rv64opsz(btype),
					rv64op, RV64GFR(regs_got.reg[0]), RV64GFR(regs_got.reg[1]),
					RV64GFR(regs_got.reg[3]),
					opts.debug_level != 0 ? str_temp(inst0->dest) : nullptr
				);
		}
			break;
		case HIROper::FNEG:
		{
			bool flag_imm = (inst0->op1.tp != HIROpnd::TEMP);
			src_temps.push_back(flag_imm ? INVALID_TEMP_NO : inst0->op1.temp);
			if (flag_imm)
				src_temps.push_back(INVALID_TEMP_NO);
			fp_src[0] = true; fp_src[1] = false;
			regs_got.regs = get_regs(iidx0, bbidx, inst0->dest, src_temps, fp_src);
			if (flag_imm)
			{
				if (opts.debug_level != 0)
				{
					if (btype == HIRBtype::DOUBLE)
						cominfo = new std::string(std::to_string(inst0->op1.imm.dval));
					else
						cominfo = new std::string(std::to_string(inst0->op1.imm.fval));
				}
				else
					cominfo = nullptr;
				append_ldimm(RV64GFR(regs_got.reg[1]),
					hirimm2int64(inst0->op1.imm, btype), cominfo);
				append_fmv4i(hirbtype2rv64opsz(btype), RV64GFR(regs_got.reg[1]),
					RV64GFR(regs_got.reg[0]));
			}
			append_farith(hirbtype2rv64opsz(btype), RV64Oper::FNEG,
				RV64GFR(regs_got.reg[0]), RV64GFR(regs_got.reg[0]),
				RV64GFR(regs_got.reg[3])
			);
		}
			break;
		case HIROper::CALL:
			generate_function_call(bbidx, iidx0);
			break;
		case HIROper::LOAD:
		{
			/*
			 * pointer type:
			 * 0: normal temp;
			 * 1: pointer to stack location that has static value;
			 * 2: external variable, integer;
			 * 3: external variable, floating-point.
			 */
			int32_t ptr_type = 0;
			if (inst0->pointer.tp == HIROpnd::TEMP)
			{
				if (tdescs_.at(inst0->pointer.temp).status & TempDesc::STATIC)
					ptr_type = 1;
				else
					src_temps.push_back(inst0->pointer.temp);
			}
			else if (inst0->pointer.tp == HIROpnd::XVAR)
			{
				if (btype == HIRBtype::FLOAT || btype == HIRBtype::DOUBLE)
				{
					src_temps.push_back(INVALID_TEMP_NO);
					ptr_type = 3;
				}
				else
					ptr_type = 2;
			}
			else
				throw std::logic_error(inststr + "invalid pointer type");
			regs_got.regs = get_regs(iidx0, bbidx, inst0->dest, src_temps);
			cominfo = opts.debug_level != 0 ? str_temp(inst0->dest) : nullptr;
			if (ptr_type == 0)
			{	// normal temp
				if (btype == HIRBtype::FLOAT || btype == HIRBtype::DOUBLE)
					append_fload(hirbtype2rv64opsz(btype), RV64GFR(regs_got.reg[0]),
						RV64GFR(regs_got.reg[3]), 0l, cominfo
					);
				else
					append_load(hirbtype2rv64opsz(btype), RV64GFR(regs_got.reg[0]),
						RV64GFR(regs_got.reg[3]), 0l, cominfo
					);
			}
			else if (ptr_type == 1)
			{	// static value
				size_t load_iidx;
				if (btype == HIRBtype::FLOAT || btype == HIRBtype::DOUBLE)
					load_iidx = append_fload(hirbtype2rv64opsz(btype), RV64GFR::SP,
						RV64GFR(regs_got.reg[3]), tdescs_.at(inst0->pointer.temp).stval,
						cominfo
					);
				else
					load_iidx = append_load(hirbtype2rv64opsz(btype), RV64GFR::SP,
						RV64GFR(regs_got.reg[3]), tdescs_.at(inst0->pointer.temp).stval,
						cominfo
					);
				stack_access_list_.push_back(load_iidx);
			}
			else if (ptr_type == 2)
			{	// external symbol, integer
				append_ldsymb(hirbtype2rv64opsz(btype),
					RV64GFR(regs_got.reg[3]), inst0->pointer.xvaridx);
			}
			else
			{	// external symbol, floating-point
				append_ldaddr(RV64GFR(regs_got.reg[0]), inst0->pointer.xvaridx);
				append_fload(hirbtype2rv64opsz(btype), RV64GFR(regs_got.reg[0]),
					RV64GFR(regs_got.reg[3]), 0l, cominfo);
			}
		}
			break;
		case HIROper::STORE:
		{
			// the value to be stored is zero
			bool flag_value_zero = false;
			/*
			 * pointer type:
			 * 0: normal temp;
			 * 1: pointer to stack location that has static value;
			 * 2: external variable.
			 */
			int32_t ptr_type = 0;
			// prepare a GPR/FPR for the value to be stored
			if (inst0->value.tp == HIROpnd::TEMP)
				src_temps.push_back(inst0->value.temp);
			else if (inst0->value.tp == HIROpnd::IMM)
			{
				if (hirimm2int64(inst0->value.imm, btype) != 0l)
					src_temps.push_back(INVALID_TEMP_NO);
				else
					flag_value_zero = true;
			}
			else
				throw UnsupportedSituation(inststr + "store external symbol");
			if (inst0->pointer.tp == HIROpnd::TEMP)
			{
				if (tdescs_.at(inst0->pointer.temp).status & TempDesc::STATIC)
					ptr_type = 1;
				else
					src_temps.push_back(inst0->pointer.temp);
			}
			else if (inst0->pointer.tp == HIROpnd::XVAR)
			{
				src_temps.push_back(INVALID_TEMP_NO);
				ptr_type = 2;
			}
			else
				throw std::logic_error(inststr + "invalid pointer type");
			// get regs
			fp_src[0] = fp_src[1] = false;
			regs_got.regs = get_regs(iidx0, bbidx, INVALID_TEMP_NO, src_temps, fp_src);
			if (flag_value_zero)
			{	/*
				 * On this situation, `src_temps` only have 1 temp for the pointer.
				 * So we should move the reg for pointer to `.reg[1]` and
				 * set the `.reg[0]` (for value) as `$zero`.
				 */
				regs_got.reg[1] = regs_got.reg[0];
				regs_got.reg[0] = uint16_t(RV64GFR::ZERO);
			}
			// generate instructions
			if (inst0->value.tp == HIROpnd::IMM && !flag_value_zero)
				append_ldimm(RV64GFR(regs_got.reg[0]),
					hirimm2int64(inst0->value.imm, btype)
				);
			if (ptr_type == 0 || ptr_type == 2)
			{	// normal temp or external symbol
				if (ptr_type == 2)
					append_ldaddr(RV64GFR(regs_got.reg[1]), inst0->pointer.xvaridx);
				//if (btype == HIRBtype::FLOAT || btype == HIRBtype::DOUBLE)
				/*
				 * We should use the type of reg rather than `btype` to decide
				 * using "store" or "fstore" instruction as floating-point
				 * immediate would be held by GPR!
				 */
				if (!RV64GFR_is_GPR(RV64GFR(regs_got.reg[0])))
					append_fstore(hirbtype2rv64opsz(btype), RV64GFR(regs_got.reg[1]),
						RV64GFR(regs_got.reg[0]), 0l
					);
				else
					append_store(
					/*
					 * If a floating-point immediate is being stored using a GPR,
					 * the operation size should be `W` rather than `S` for `float`!
					 */
						hirbtype2rv64opsz(btype == HIRBtype::FLOAT ? HIRBtype::I32 : btype),
						RV64GFR(regs_got.reg[1]), RV64GFR(regs_got.reg[0]), 0l
					);
			}
			else if (ptr_type == 1)
			{	// static value
				size_t load_iidx;
				if (!RV64GFR_is_GPR(RV64GFR(regs_got.reg[0])))
					load_iidx = append_fstore(hirbtype2rv64opsz(btype), RV64GFR::SP,
						RV64GFR(regs_got.reg[0]), tdescs_.at(inst0->pointer.temp).stval
					);
				else
					load_iidx = append_store(
						hirbtype2rv64opsz(btype == HIRBtype::FLOAT ? HIRBtype::I32 : btype),
						RV64GFR::SP, RV64GFR(regs_got.reg[0]),
						tdescs_.at(inst0->pointer.temp).stval
					);
				stack_access_list_.push_back(load_iidx);
			}
		}
			break;
		case HIROper::ICMP:
		{
			// if the operand 2 is zero
			bool flag_opnd2_zero = false;
			/*
			 * Temporarily suppose that an `icmp` instruction must be followed
			 * by a `br` (conditional) instruction, and the temp as `cond`
			 * is only referenced here.
			 */
			if (inst1->oper != HIROper::BRc ||
				ircode_->funct(hirfidx_).temps.at(inst1->cond).localof != bbidx)
				throw UnsupportedSituation("icmp is not followed by br");
			// try to find the next instruction of `inst1`
			std::vector<HIRInst>::const_iterator inst2;
			for (inst2 = inst1 + 1l;
				inst2 != funct_ir.end() && inst2->oper == HIROper::EMPTY_LINE; ++inst2)
				;
			if (inst2 == funct_ir.end())
			// If `inst1` is the last, set `inst2` as `inst1`
				inst2 = inst1;
			RV64Oper rv64op;
			rv64op = (
				inst0->icond == ICMPCond::EQ ? RV64Oper::BEQ
				: inst0->icond == ICMPCond::NE ? RV64Oper::BNE
				: inst0->icond == ICMPCond::SGE ? RV64Oper::BGE
				: inst0->icond == ICMPCond::SGT ? RV64Oper::BGT
				: inst0->icond == ICMPCond::SLE ? RV64Oper::BLE
				: RV64Oper::BLT
			);
			src_temps.push_back(inst0->op1.tp == HIROpnd::TEMP
				? inst0->op1.temp : INVALID_TEMP_NO);
			if (inst0->op2.tp == HIROpnd::IMM
				&& hirimm2int64(inst0->op2.imm, btype) == 0l)
				flag_opnd2_zero = true;
			if (!flag_opnd2_zero)
				src_temps.push_back(inst0->op2.tp == HIROpnd::TEMP
					? inst0->op2.temp : INVALID_TEMP_NO);
			fp_src[0] = fp_src[1] = false;
			regs_got.regs = get_regs(iidx0, bbidx, INVALID_TEMP_NO, src_temps, fp_src);
			if (inst0->op1.tp != HIROpnd::TEMP)
				append_ldimm(RV64GFR(regs_got.reg[0]),
					hirimm2int64(inst0->op1.imm, btype));
			if (flag_opnd2_zero)
				regs_got.reg[1] = uint16_t(RV64GFR::ZERO);
			else if (inst0->op2.tp != HIROpnd::TEMP)
				append_ldimm(RV64GFR(regs_got.reg[1]),
					hirimm2int64(inst0->op2.imm, btype));
			save_temps_to_stack(bbidx);
			temps_are_saved = true;
			// generate conditional branch instruction
			if (inst2->oper != HIROper::LABEL ||
				(inst2->labelno != inst1->iftrue && inst2->labelno != inst1->iffalse))
			{
				append_cbranch(rv64op, RV64GFR(regs_got.reg[0]),
					RV64GFR(regs_got.reg[1]), inst1->iftrue);
				append_jump(inst1->iffalse);
			}
			else if (inst2->labelno == inst1->iftrue)
			{
				// reverse the condition
				rv64op = (
					rv64op == RV64Oper::BEQ ? RV64Oper::BNE
					: rv64op == RV64Oper::BNE ? RV64Oper::BEQ
					: rv64op == RV64Oper::BGE ? RV64Oper::BLT
					: rv64op == RV64Oper::BGT ? RV64Oper::BLE
					: rv64op == RV64Oper::BLE ? RV64Oper::BGT
					: RV64Oper::BGE
				);
				append_cbranch(rv64op, RV64GFR(regs_got.reg[0]),
					RV64GFR(regs_got.reg[1]), inst1->iffalse);
			}
			else	// `.. == inst1->iffalse`
				append_cbranch(rv64op, RV64GFR(regs_got.reg[0]),
					RV64GFR(regs_got.reg[1]), inst1->iftrue);
			// force the `for` loop to exit
			iidx1 = bb_end + 1u;
		}
			break;
		case HIROper::EMPTY_LINE:
			break;
		case HIROper::LABEL:
			append_label(inst0->labelno);
			break;
		default:
			throw UnsupportedSituation(inststr + std::to_string(uint32_t(op)));
			break;
		}
	}
	if (!temps_are_saved)
		// save temps as a BB ends
		save_temps_to_stack(bbidx);
	bb_stacks_.at(bbidx).sp -= bb_stacks_.at(bbidx).nargs_on_stack * RV64_GFR_SIZE;
}

void RV64Funct::generate_function_call(uint32_t bbidx, size_t inst_idx)
{
	// argument passing infomation
	std::vector<RV64ArgPassing> argspassing;
	/*
	 * If a reg using to pass an argument has been occupied
	 * by a temp that has been allocated this reg,
	 * store it in the set to restore it later.
	 */
	std::vector<HIRTempno> temps_tobe_restored;
	// Released temp because of argument passing
	HIRTempno trel;

	uint32_t nargs_GPR = 0u, nargs_FPR = 0u, nargs_stack = 0u, aidx;
	// this call instruction
	const HIRInst & inst = ircode_->funct(hirfidx_).ircode.at(inst_idx);

	if (inst.oper != HIROper::CALL)
		throw std::logic_error("not a `call` instruction");
	// when `call` instruction is generated, `$ra` must be saved
	saved_regs_[RV64GFR::RA] = INVALID_STLOC;
	if (inst.pfargs != nullptr)
	{
		// all function arguments
		const std::vector<HIRFarg> &args = *inst.pfargs;
		argspassing.resize(args.size());
		/*
		 * Determine by which the `adix`-th argument is passed
		 * according to RV64 ABI.
		 */
		for (aidx = 0u; aidx < args.size(); ++aidx)
		{
			if (args.at(aidx).type == HIRBtype::DOUBLE
				|| args.at(aidx).type == HIRBtype::FLOAT)
			{	// floating-point argument
				if (nargs_FPR >= RV64_AFPR)
				{	// use stack
					argspassing.at(aidx).use_reg = false;
					argspassing.at(aidx).stoff = nargs_stack * RV64_GFR_SIZE;
					++nargs_stack;
				}
				else
				{
					argspassing.at(aidx).use_reg = true;
					argspassing.at(aidx).reg = RV64GFR(unsigned(RV64GFR::FA0) + nargs_FPR);
					++nargs_FPR;
				}
			}
			else
			{	// integer argument
				if (nargs_GPR >= RV64_AGPR)
				{	// use stack
					argspassing.at(aidx).use_reg = false;
					argspassing.at(aidx).stoff = nargs_stack * RV64_GFR_SIZE;
					++nargs_stack;
				}
				else
				{
					argspassing.at(aidx).use_reg = true;
					argspassing.at(aidx).reg = RV64GFR(unsigned(RV64GFR::A0) + nargs_GPR);
					++nargs_GPR;
				}
			}
			// Hundreds of arguments that the offset relative to `$sp` is too large...
			if (argspassing.at(aidx).use_reg == false
				&& argspassing.at(aidx).stoff > IMM12_ABS_MAX)
				throw UnsupportedSituation(
					ircode_->funct(inst.funcidx).id + ": too many arguments"
				);
		}
		// update the counter!
		if (bb_stacks_.at(bbidx).nargs_on_stack < nargs_stack)
			bb_stacks_.at(bbidx).nargs_on_stack = nargs_stack;
		while (aidx-- > 0u)
		{
			// the current argument
			const HIRFarg & arg = args.at(aidx);
			if (argspassing.at(aidx).use_reg)
			{	// `aidx`-th argument can be passed using GPR/FPR
				// the reg using to pass the argument
				const RV64GFR areg = argspassing.at(aidx).reg;
				if (arg.arg.tp == HIROpnd::TEMP)
				{	// the argument is a temp
					if ((tdescs_.at(arg.arg.temp).status & TempDesc::REG) == 0
						|| tdescs_.at(arg.arg.temp).reg != areg)
					{	// the temp is not in the reg
						// release the reg forcibly
						if ((trel = get_regs(areg, bbidx)) != INVALID_TEMP_NO &&
							(tdescs_.at(trel).status & TempDesc::ALLOCATED) != 0)
						{	// If the temp held by the reg is allocated...
							std::set<HIRTempno> temps_coalesced;
							if ((ircode_->funct(hirfidx_).temps.at(trel).localof
									== INVALID_BB_IDX
								|| 1ul == (3ul & ircode_->funct(hirfidx_).
								get_temp_future_use(trel, inst_idx, bbidx))))
								temps_tobe_restored.push_back(trel);
							/*
							 * If `trel` (the temp held by the reg) is non-local or live,
							 * we need to save and then restore it; however, if `trel` is
							 * not live but some other temps share the same reg with it,
							 * we should also check the liveness of them...
							 */
							else if ((temps_coalesced =
								get_temps_sharing_reg(trel)).empty() == false)
								for (auto st : temps_coalesced)
									if ((ircode_->funct(hirfidx_).temps.at(st).localof
									== INVALID_BB_IDX
									|| 1ul == (3ul & ircode_->funct(hirfidx_).
									get_temp_future_use(st, inst_idx, bbidx))))
									{
										temps_tobe_restored.push_back(st);
										break;
									}
						}
						load_temp_to_reg(arg.arg.temp, areg);
					}
				}
				else
				{	// the argument is an imm or external symbol
					if ((trel = get_regs(areg, bbidx)) != INVALID_TEMP_NO &&
						(tdescs_.at(trel).status & TempDesc::ALLOCATED) != 0)
					{	// If the temp held by the reg is allocated...
						std::set<HIRTempno> temps_coalesced;
						if ((ircode_->funct(hirfidx_).temps.at(trel).localof
								== INVALID_BB_IDX
							|| 1ul == (3ul & ircode_->funct(hirfidx_).
							get_temp_future_use(trel, inst_idx, bbidx))))
							temps_tobe_restored.push_back(trel);
						/*
						 * If `trel` (the temp held by the reg) is non-local or live,
						 * we need to save and then restore it; however, if `trel` is
						 * not live but some other temps share the same reg with it,
						 * we should also check the liveness of them...
						 */
						else if ((temps_coalesced =
							get_temps_sharing_reg(trel)).empty() == false)
							for (auto st : temps_coalesced)
								if ((ircode_->funct(hirfidx_).temps.at(st).localof
								== INVALID_BB_IDX
								|| 1ul == (3ul & ircode_->funct(hirfidx_).
								get_temp_future_use(st, inst_idx, bbidx))))
								{
									temps_tobe_restored.push_back(st);
									break;
								}
					}
					if (arg.arg.tp == HIROpnd::XVAR)
					{	// external variable
						append_ldaddr(areg, arg.arg.xvaridx);
					}
					else
					{	// immediate
						if (arg.type == HIRBtype::DOUBLE || arg.type == HIRBtype::FLOAT)
						{	// floating-point imm
							if (rdescs_[uint32_t(HRV64Generator::aux_reg)]
								.status != RegDesc::RESERVED)
								throw UnsupportedSituation(
									std::to_string(uint32_t(HRV64Generator::aux_reg))
									+ " is not reserved"
								);
							// load it to `$ra` then move to FPR `areg`
							append_ldimm(HRV64Generator::aux_reg,
								hirimm2int64(arg.arg.imm, arg.type));
							append_fmv4i(hirbtype2rv64opsz(arg.type),
								HRV64Generator::aux_reg, areg);
						}
						else	// integer imm
							append_ldimm(areg, hirimm2int64(arg.arg.imm, arg.type));
					}
				}
			}
			else
			{	// stack must be used to pass the argument
				if (arg.arg.tp != HIROpnd::TEMP ||
					(tdescs_.at(arg.arg.temp).status & TempDesc::REG) == 0)
				{	// the arg is not a temp or the temp is not in a reg
					// use `$a0` as "scratch" even if it is a floating-point temp
					//const RV64GFR Scratch = RV64GFR::A0;
					/*
					 * Use `$ra` instead as global register allocation
					 * is implemeted, which might cause `$a0` be allocated.
					 */
					const RV64GFR Scratch = HRV64Generator::aux_reg;
					if (rdescs_[uint32_t(Scratch)].status != RegDesc::RESERVED)
						get_regs(Scratch, bbidx);
					if (arg.arg.tp == HIROpnd::TEMP)
						load_temp_to_reg(arg.arg.temp, Scratch);
					else if (arg.arg.tp == HIROpnd::IMM)
						// imm
						append_ldimm(Scratch, hirimm2int64(arg.arg.imm, arg.type));
					else	// external symbol
						append_ldaddr(Scratch, arg.arg.xvaridx);
					/*
					* Although this is a stack access, don't push
					* it into `_stack_access_list[]`!
					*/
					append_store(hirbtype2rv64opsz(arg.type), RV64GFR::SP,
						Scratch, int64_t(argspassing.at(aidx).stoff));
				}
				else
				{	// a temp already in a reg
					if (RV64GFR_is_GPR(tdescs_.at(arg.arg.temp).reg))
						append_store(hirbtype2rv64opsz(arg.type), RV64GFR::SP,
						tdescs_.at(arg.arg.temp).reg, int64_t(argspassing.at(aidx).stoff)
					);
					else
						append_fstore(hirbtype2rv64opsz(arg.type), RV64GFR::SP,
						tdescs_.at(arg.arg.temp).reg, int64_t(argspassing.at(aidx).stoff)
					);
				}
			}
		}	// Now all arguments have been prepared!
	}
	// Save all regs that may be modified
	for (auto reg : RV64GFR_caller_saved)
	{
		RegDesc & rdesc = rdescs_[uint32_t(reg)];
		if (rdesc.status == RegDesc::TEMP)
		{
			if ( ! (inst.dest != INVALID_TEMP_NO &&
				(tdescs_.at(inst.dest).status & TempDesc::ALLOCATED) != 0 &&
				(tdescs_.at(inst.dest).reg == reg))
				/*
				 * If the dest temp of the `call` instruction is allocated the same
				 * reg as `reg`, then it means that the value held by `reg` is
				 * NOT live here. So, there is no need to save it!
				 * Otherwise, the return value will be overwritten when restoring it!
				 */
				&&
				(ircode_->funct(hirfidx_).temps.at(rdesc.tempno).localof == INVALID_BB_IDX
				|| (inst.dest != rdesc.tempno &&
				1ul == (3ul & ircode_->funct(hirfidx_).
				get_temp_future_use(rdesc.tempno, inst_idx + 1u, bbidx)))))
			{	/*
				 * If the reg holds a temp that is non-local, or the temp will
				 * be referenced in the future in this BB,
				 * write it back to stack and releasee the reg
				 * (also clear the `REG` flag in its desc).
				 */
				temp_writeback(rdesc.tempno, bbidx);
				if (tdescs_.at(rdesc.tempno).status & TempDesc::ALLOCATED)
					temps_tobe_restored.push_back(rdesc.tempno);
			}
			else	// otherwise just clear the `REG` flag
				tdescs_.at(rdesc.tempno).status &= ~TempDesc::REG;
		}
		if (rdesc.status != RegDesc::RESERVED)
		// Don't modify reserved reg, such as `$ra`!
			rdesc.status = RegDesc::FREE;
	}
	append_call(inst.funcidx);
	if (inst.dest != INVALID_TEMP_NO)
	{	// the return value is used
		// the reg holding the return value
		const RV64GFR ret_reg = (
			ircode_->funct(inst.funcidx).rettype == HIRBtype::DOUBLE
			|| ircode_->funct(inst.funcidx).rettype == HIRBtype::FLOAT
			? RV64GFR::FA0 : RV64GFR::A0
		);
		if (tdescs_.at(inst.dest).status & TempDesc::ALLOCATED)
		{/*
		 * If the dest temp has been allocated a reg,
		 * move the return value to it.
		 */
			if (ret_reg == RV64GFR::FA0)
				append_fmove(hirbtype2rv64opsz(ircode_->funct(inst.funcidx).rettype),
					RV64Oper::FMV, ret_reg, tdescs_.at(inst.dest).reg);
			else
				append_arlo(RV64Oper::ADD, ret_reg, RV64GFR::ZERO,
					tdescs_.at(inst.dest).reg, RV64Opsz::D);
			if (!rdescs_[uint32_t(tdescs_.at(inst.dest).reg)].flag_allocated)
				exc_reg_not_allocated(inst.dest, tdescs_.at(inst.dest).reg);
			rdescs_[uint32_t(tdescs_.at(inst.dest).reg)].status = RegDesc::TEMP;
			rdescs_[uint32_t(tdescs_.at(inst.dest).reg)].tempno = inst.dest;
			tdescs_.at(inst.dest).status = TempDesc::REG | TempDesc::ALLOCATED;
		}
		else
		{
			rdescs_[uint32_t(ret_reg)].status = RegDesc::TEMP;
			rdescs_[uint32_t(ret_reg)].tempno = inst.dest;
			tdescs_.at(inst.dest).status = TempDesc::REG;
			tdescs_.at(inst.dest).reg = ret_reg;
		}
	}
	// Restore registers that are allocated
	for (auto tr : temps_tobe_restored)
		if (inst.dest == INVALID_TEMP_NO ||
			tdescs_.at(tr).reg != tdescs_.at(inst.dest).reg)
		{	// Don't overwrite the return value!
			load_temp_to_reg(tr, tdescs_.at(tr).reg);
			tdescs_.at(tr).status = TempDesc::REG | TempDesc::ALLOCATED;
			if (!rdescs_[uint32_t(tdescs_.at(tr).reg)].flag_allocated)
				exc_reg_not_allocated(tr, tdescs_.at(tr).reg);
			rdescs_[uint32_t(tdescs_.at(tr).reg)].status = RegDesc::TEMP;
			rdescs_[uint32_t(tdescs_.at(tr).reg)].tempno = tr;
		}
}

/*
 * Determine the offsets of stack access instructions
 * listed in `_stack_access_list[]`.
 */
void RV64Funct::stack_access_backpatch(void)
{
	size_t i, j, n, iidx;
	// hypothetical stack address
	stloc_t hyp_addr;
	// offset relative to `_common_sp`
	int64_t offset;
	// for insertion
	RV64Inst aux_inst;
	n = stack_access_list_.size();
	for (i = 0ul; i < n; ++i)
	{
		iidx = stack_access_list_.at(i);
		RV64Oper inst_op = code_.at(iidx).oper;
		/*
		 * Up to now, only this five kinds of instructions
		 * might appear in `_stack_access_list[]`.
		 * And the `base` or `src1` must be `$sp`.
		 * NOTE: if more kinds of instructions may appear in
		 * the list, remember to check the similar comparisons
		 * in `generate_prologue_and_epilogue()`.
		 */
		if ((inst_op == RV64Oper::FLOAD || inst_op == RV64Oper::FSTORE
			|| inst_op == RV64Oper::LOAD || inst_op == RV64Oper::STORE)
			&& code_.at(iidx).base == RV64GFR::SP)
			hyp_addr = code_.at(iidx).off12;
		else if (inst_op == RV64Oper::ADDi
			&& code_.at(iidx).src1 == RV64GFR::SP)
			hyp_addr = code_.at(iidx).imm12;
		else
			throw std::logic_error(
				std::string("invalid inst in _stack_acc...: ")
				+ std::to_string(iidx) + ' ' + std::to_string(uint32_t(inst_op))
			);
		if (hyp_addr < common_sp_)
			throw std::logic_error(
				std::string("stack access out of range: ")
				+ std::to_string(hyp_addr)
			);
		offset = hyp_addr - common_sp_;
		if (is_imm12(offset))
		{
			if (inst_op == RV64Oper::ADDi)
				code_.at(iidx).imm12 = offset;
			else
				code_.at(iidx).off12 = offset;
		}
		else
		{	// use `$ra` to hold offset
			if (rdescs_[uint32_t(HRV64Generator::aux_reg)].status != RegDesc::RESERVED)
				throw UnsupportedSituation(
					std::to_string(uint32_t(HRV64Generator::aux_reg))
					+ " is not reserved"
				);
			saved_regs_[HRV64Generator::aux_reg] = INVALID_STLOC;
			if (inst_op == RV64Oper::ADDi)
			{
				code_.at(iidx).src1 = HRV64Generator::aux_reg;
				code_.at(iidx).imm12 = 0l;
			}
			else
			{
				code_.at(iidx).base = HRV64Generator::aux_reg;
				code_.at(iidx).off12 = 0l;
			}
			// insert: `add ra, ra, sp`
			aux_inst.oper = RV64Oper::ADD;
			aux_inst.size = RV64Opsz::D;
			aux_inst.src1 = HRV64Generator::aux_reg;
			aux_inst.src2 = RV64GFR::SP;
			aux_inst.dest = HRV64Generator::aux_reg;
			code_.insert(code_.begin() + iidx, aux_inst);
			// insert: `li ra, offset`
			aux_inst.oper = RV64Oper::LI;
			aux_inst.imm64 = offset;
			code_.insert(code_.begin() + iidx, aux_inst);
			// update indexes
			for (j = 0ul; j < n; ++j)
				if (stack_access_list_.at(j) >= iidx)
					stack_access_list_.at(j) += 2u;
		}
	}
}

/*
 * After `_saved_regs` are known, now we can allocate
 * stack space for them, and then decide `_common_sp`, etc.
 */
void RV64Funct::generate_prologue_and_epilogue(void)
{
	std::vector<RV64Inst> prologue, epilogue;
	RV64Inst aux_inst(RV64Oper::ADDi);
	// `$ra` is always saved
	if (RV64GFR_is_callee_saved(HRV64Generator::aux_reg))
		saved_regs_[HRV64Generator::aux_reg] = INVALID_STLOC;
	// size of the space to save callee-saved regs, must ALIGNED
	uint32_t saved_size = roundup(saved_regs_.size() * RV64_GFR_SIZE, RV64_SP_ALIGN),
		frame_size;
	// offset for load/store instructions
	int64_t offset;
	// saved location
	uint32_t saved_loc = frame_base_ - RV64_GFR_SIZE;
	// number of instructions in prologue
	size_t nprologue;

	if (saved_size > IMM12_ABS_MAX)
		throw UnsupportedSituation("saved regs overflow");
	static_top_ -= saved_size;

	// Calculate `_common_sp`
	common_sp_ = static_top_;
	for (auto & bbstack : bb_stacks_)
		if ((bbstack.sp -= saved_size) < common_sp_)
			common_sp_ = bbstack.sp;
	common_sp_ = rounddown(common_sp_, RV64_SP_ALIGN);

	if (common_sp_ < saved_size)
		throw UnsupportedSituation("stack overflow: 1");

	frame_size = frame_base_ - common_sp_;
	/*
	 * If the stack frame size is too large to sub
	 * in one `addi` instruction, we need to
	 * sub `$sp` twice.
	 */
	aux_inst.src1 = aux_inst.dest = RV64GFR::SP;
	if (frame_size > IMM12_ABS_MAX)
	{
		// initial value of `offset`
		offset = int64_t(saved_size - RV64_GFR_SIZE);
		aux_inst.imm12 = -int64_t(saved_size);
	}
	else
	{
		offset = int64_t(frame_size - RV64_GFR_SIZE);
		aux_inst.imm12 = -int64_t(frame_size);
	}
	prologue.push_back(aux_inst);
	aux_inst.imm12 = -aux_inst.imm12;
	epilogue.insert(epilogue.begin(), aux_inst);

	// generate instructions to save and restore regs
	aux_inst.base = RV64GFR::SP;
	for (auto & sreg : saved_regs_)
	{
		sreg.second = saved_loc;
		aux_inst.oper = (RV64GFR_is_GPR(sreg.first) ? RV64Oper::STORE : RV64Oper::FSTORE);
		aux_inst.src2 = aux_inst.dest = sreg.first;
		aux_inst.off12 = offset;
		prologue.push_back(aux_inst);
		aux_inst.oper = (RV64GFR_is_GPR(sreg.first) ? RV64Oper::LOAD : RV64Oper::FLOAD);
		epilogue.insert(epilogue.begin(), aux_inst);
		saved_loc -= RV64_GFR_SIZE;
		offset -= RV64_GFR_SIZE;
	}
	// sub `$sp` to `_common_sp`
	if (frame_size > IMM12_ABS_MAX)
	{
		const RV64GFR aux_reg = RV64GFR::T1;
		aux_inst.oper = RV64Oper::LI;
		aux_inst.dest = aux_reg;
		aux_inst.imm64 = int64_t(frame_size - saved_size);
		// append `li t1, <size>`
		prologue.push_back(aux_inst);
		epilogue.insert(epilogue.begin(), aux_inst);
		aux_inst.oper = RV64Oper::SUB;
		aux_inst.src1 = aux_inst.dest = RV64GFR::SP;
		aux_inst.src2 = aux_reg;
		// append `sub sp, sp, t1`
		prologue.push_back(aux_inst);
		aux_inst.oper = RV64Oper::ADD;
		// insert `add sp, sp, t1`
		epilogue.insert(epilogue.begin() + 1, aux_inst);
	}
	aux_inst.oper = RV64Oper::RET;
	// append `ret`
	epilogue.push_back(aux_inst);
	aux_inst.oper = RV64Oper::LABEL;
	aux_inst.labelno = epilogue_;
	epilogue.insert(epilogue.begin(), aux_inst);
	// insert prologue and append epilogue
	code_.insert(code_.begin(), prologue.begin(), prologue.end());
	code_.insert(code_.end(), epilogue.begin(), epilogue.end());
	nprologue = prologue.size();
	/*
	 * update indexes in `_stack_access_list[]` as we have
	 * inserted prologue (which modifies indexes) and allocated
	 * space for saved regs (which modifies stack location).
	 */
	uint32_t iidx_max = 0u;
	for (auto & iidx : stack_access_list_)
	{
		iidx += nprologue;
		/*
		 * Indexes in `_stack_access_list[]` should be increasing
		 * because they are pushed into it as the instructions are
		 * generated in order.
		 * We insert a check here because a bug was caused by pushing
		 * an invalid index (0) into it!
		 */
		if (iidx > iidx_max)
			iidx_max = iidx;
		else
			throw std::logic_error(
				"indexes in _stack_access_list is not increasing: "
				+ std::to_string(iidx)
			);
		RV64Oper inst_op = code_.at(iidx).oper;
		/*
		 * NOTE: if more kinds of instructions may appear in
		 * the list, remember to check comparisons here.
		 */
		if (inst_op == RV64Oper::ADDi)
		{
			/*
			 * If the stack location address is higher or equal to
			 * `_frame_base`, don't modify it as it is not influenced
			 * by inserting saved regs. (Actually, they are arguments
			 * that cannot be passed by regs.)
			 */
			if (code_.at(iidx).imm12 < frame_base_)
				code_.at(iidx).imm12 -= int64_t(saved_size);
		}
		else
		{
			if (code_.at(iidx).off12 < frame_base_)
				code_.at(iidx).off12 -= int64_t(saved_size);
		}
	}
}

// Generate code for the function!
void RV64Funct::generate_code()
{
	size_t i;
	// set it because it will be printed in debug mode
	bb_stacks_.at(0ul).sp = static_top_;

	for (i = 1u; i < ircode_->funct(hirfidx_).basic_blocks.size(); ++i)
	{
		if (opts.opt_cfg_cleanup &&
			ircode_->funct(hirfidx_).basic_blocks.at(i).reachable == false)
			// Clean unreachable code
			bb_stacks_.at(i).sp = static_top_;
		else
			generate_bb_code(i);
	}
	generate_prologue_and_epilogue();
	stack_access_backpatch();
}